All files / web/src/app/api/images/[category]/[filename] route.ts

0% Statements 0/58
0% Branches 0/1
0% Functions 0/1
0% Lines 0/58

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59                                                                                                                     
/**
 * API route for serving persistent generated images.
 *
 * GET /api/images/[category]/[filename]
 *
 * Serves images from data/generated-images/{category}/{filename}
 * (NFS-backed in production). No auth required.
 */

import { NextResponse } from 'next/server'
import { withAuth } from '@/lib/auth/withAuth'
import { readPersistentImage } from '@/lib/image-storage'

const CONTENT_TYPES: Record<string, string> = {
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.jpeg': 'image/jpeg',
  '.webp': 'image/webp',
  '.gif': 'image/gif',
  '.svg': 'image/svg+xml',
}

export const GET = withAuth(async (_request, { params }) => {
  try {
    const { category, filename } = (await params) as { category: string; filename: string }

    // Validate path segments to prevent directory traversal
    if (
      !category ||
      !filename ||
      category.includes('/') ||
      category.includes('..') ||
      filename.includes('/') ||
      filename.includes('..')
    ) {
      return NextResponse.json({ error: 'Invalid parameters' }, { status: 400 })
    }

    const result = await readPersistentImage(category, filename)
    if (!result) {
      return new NextResponse(null, { status: 404 })
    }

    const ext = filename.substring(filename.lastIndexOf('.')).toLowerCase()
    const contentType = CONTENT_TYPES[ext] ?? 'application/octet-stream'

    return new NextResponse(new Uint8Array(result.buffer), {
      headers: {
        'Content-Type': contentType,
        'Content-Length': result.sizeBytes.toString(),
        'Cache-Control': 'public, max-age=31536000, immutable',
      },
    })
  } catch (error) {
    console.error('Error serving generated image:', error)
    return new NextResponse(null, { status: 500 })
  }
})